<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    // this指向当前所在对象
    var A = {
        describe: getName,
    };
    var B = {
        describe: getName,
    };
    function getName() {
        return this;
    }
    //  X.describe表示一个函数，而X.describe()表示一个值
    // 指向A对象
    console.log(A.describe, A.describe());
    // 指向B对象
    console.log(B.describe, B.describe());
    console.log(typeof A.describe); // function
    // 因为此处f处于全局环境中，所以this指向全局变量window
    var f = A.describe;
    console.log(f(), f() === window); // true;
    // 变量得到的是一个内存地址
    // 全局变量中使用this，代指整个js文件（window对象）
    console.log(this.getName()); // 就是window对象
    // 构造函数中使用this，代指实例对象
    var Person = function (name, age) {
        this.name = name;
        this.age = age;
        console.log(this);
    };
    var bill = new Person("bill", 24); // Person {name: "bill", age: 24}
    // 对象的方法包含this，则代指当前方法 运行时 所在的对象
    var tom = {
        name: "Tom",
        getName: function () {
            console.log(this.name);
        },
        // 嵌套的对象
        getThisName: {
            age: function () {
                console.log(this.name);
            }
        },
        involve: function () {
            // 嵌套的函数(立即执行函数）
            var linux;
            linux = function () {
                console.log(this);
            }();
        }
    };
    var name = "全局";
    tom.getName(); // Tom
    // 注意：
    // 下面这种情况表明： 右边为一个函数，然后将该函数赋值给左边的变量，所以 运行时 环境是window，而window中name值为"全局"
    var getname = tom.getName;
    getname(); // 全局
    // 以下三种函数也是如此：只要前面括号内不仅只一个tom.getName,则运行环境即全局
    (tom.getName = tom.getName)();
    (1, tom.getName)();
    (tom.getName || false)();
    // this只指向当前方法所在对象，并不指向祖先
    tom.getThisName.age(); // undefined,此次this指向tom的子对象getThisName，且其没有age属性

    // 为了避免混淆，避免在函数中包含多层this
    // 避免在嵌套函数中直接使用this
    console.log(tom.involve()); // window对象， this指向window，并且由于该函数无返回值，所以console输出undefined


    // 最常见的解决方法：
    // 解决方法：在函数内部固定this（将this赋值给另一变量），然后嵌套函数就可以引用了
    var resolveInnerFuncThis = {
        name: "大海",
        week: [1, 2, 3, 4, 5, 6, 7],
        getName: function () {
            var that = this;
            var getThis = function innerFunc() {
                console.log(that, this); // that指向resolveInnerFuncThis, this指向window
            }();
        },
        useStrict: function () {
            "use strict";
            var inner = function innerFunc() {
                console.log(this);
            }();
        },
        getArr: function () {
            this.week.forEach(function (value) {
                console.log(this.name, value);
            }, this);
        }
    };
    resolveInnerFuncThis.getName();
    // 当使用use strict时，嵌套函数中this值为undefined
    resolveInnerFuncThis.useStrict();
    // 若嵌套函数为一数组方法，可以固定第二个参数为this可指向该对象而不是全局
    resolveInnerFuncThis.getArr(); // 获取：大海 1/2/...
    // 事件的回调函数中this指向事件本身的DOM对象

    // 绑定this的方法： call，apply，bind
    // A.call(obj, arg1, arg2,...): 对象obj执行A，并且A内的this指向obj，从arg1开始的参数为调用时传入的参数
    // 当第一个参数为空（没有参数）/null/undefined时，默认指向全局对象
    // 参数为原始值，会自动转为包装对象，然后执行call方法
    var originValue = function (x, y, z) {
        this.x = x;
        this.y = y;
        this.z = z;
        this.get = {
            x: x,
            y: y,
            z: z,
            obj: this
        };
        return this.get;
    };
    var num5 = originValue.call(5, 10, 11, 12);
    console.log(num5); // {x: 10, y: 11, z: 12, obj: Number}
    console.log(originValue.call("Ioiy Aig", "xxx", "yyy", "yyy")); // {x: "xxx", y: "yyy", z: "yyy", obj: String}
    // call的应用：调用对象的原生方法：当对象继承的属性被其改写，则判断将会失误
    // 比如改写了hasOwnProperty方法（判断某属性是继承的还是已有的）
    var obj = {};
    console.log(obj.hasOwnProperty("toString")); // false
    obj.hasOwnProperty = function () {
        // 不管是不是，都返回true
        return true;
    };
    console.log(obj.hasOwnProperty("toString")); // true
    // 使用call就相当于调用了原来继承的方法，而跳过重置的方法
    console.log(Object.prototype.hasOwnProperty.call(obj, "toString")); //false

    // A.apply(obj, [arg1,arg2,...]): 与call相似，只不过将参数封在了数组内(依次传入数组元素至调用参数内）
    // 当第一个参数为null/undefined时，默认指向全局对象
    // 找最大数：
    console.log(Math.min.apply({}, [12, 1, -12, 89, Infinity, 0])); // 12
    // 将空元素变为undefined：array构造函数会将空元素变为undefined，而数组的forEach方法会跳过空元素
    console.log(Array.apply(undefined, [1,,2,,3,,4,,5])); // [1, undefined, 2, undefined, 3, undefined, 4, undefined, 5]
    // 将类似数组的对象（arguments）转为数组: 利用数组的splice方法，前提是必须要有length属性和值
    // 即key为数组索引(可以转为数字的字符串），value为值
    console.log(Array.prototype.slice.call({1: "a", "2": 111, null: undefined, length: 10}));//  [empty, "a", 111, empty × 7]

    // bind(obj, arg1, arg2,...)： 绑定某个对象，每运行一次都返回一个新函数
    // 当第一个参数为null/undefined时，默认指向全局对象
    var multiArg = function (x, y, z) {
        console.log(this, x, y, z);
    };
    // 传入参数可不必一下子全部绑定传入，下次再执行的时候可将剩余的参数传入
    var aa = multiArg.bind(null, 10, 11);
    aa(12); // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …} 10 11 12
    // bind()函数是返回一个新的函数
    var counter = {
        count: 0,
        add: function() {
            "use strict";
            // count变量需要使用this调用
            // 否则会导致referenceError：count not define
            this.count++;
        }
    };
    // 使用回调函数
    function callIt(callback) {
        callback();
    }
    callIt(counter.add.bind(counter));
    console.log(counter.count); // 1
    // 数组方法内部的this指向
    var innerArrThis = {
        name: "张三",
        times: [1, 3, 5],
        print: function() {
            // 此处若不在foreach的第二个参数指向this，则函数内部的this指向全局对象
            this.times.forEach(function (n){
                console.log(this.name, n);
            }, this)
        }
    };
    innerArrThis.print(); // 张三 1/3/5
    // 改写JavaScript中的原生方法: 结合使用bind和call方法
    var slice = Function.prototype.call.bind(Array.prototype.slice);
    /**解析：
     1，相当于Array.prototype.slice.call(Arr, arg1, arg2,...)
     2，Arr.slice(arg1, arg2,...)
     */
    console.log(slice([1, 2, 12, 15], 0, 2));

    function ff() {
        console.log(this.v);
    }
    var o = { v: 123 };
    // 理解：
    // Function.prototype.bind.call(f, o)
    //f.band(o)
    var bind = Function.prototype.call.bind(Function.prototype.bind);
    // 此处是：相当于：o调用f的方法
    bind(ff, o)()//  123

</script>
</body>
</html>